/**
 * Copyright © 2011 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.creativor.rayson.transport.client;

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import org.creativor.rayson.api.TransferArgument;
import org.creativor.rayson.api.TransferSocket;
import org.creativor.rayson.exception.IllegalServiceException;
import org.creativor.rayson.exception.NetWorkException;
import org.creativor.rayson.exception.ServiceNotFoundException;
import org.creativor.rayson.exception.UnsupportedVersionException;
import org.creativor.rayson.transport.common.ConnectionState;
import org.creativor.rayson.transport.common.ProtocolType;

// TODO: Auto-generated Javadoc
/**
 * An client to access transfer service.
 * 
 * @author Nick Zhang
 */
public final class TransportClient
{
	private static TransportClient singleton = new TransportClient();
	
	/**
	 * Gets the singleton.
	 * 
	 * @return the singleton
	 */
	public static TransportClient getSingleton()
	{
		return singleton;
	}
	
	private ConnectionManager connectionManager;
	private RpcConnector connector;
	private Listener listener;
	private PacketManager packetManager;
	
	private TransportClient()
	{
		connector = new RpcConnector(this);
		connectionManager = new ConnectionManager();
		packetManager = new PacketManager();
		listener = new Listener(connectionManager);
	}
	
	/**
	 * Create a new {@link RpcConnection}.
	 * 
	 * @param serverAddress
	 *            the server address
	 * @return the connection
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @throws ConnectException
	 *             the connect exception
	 */
	RpcConnection createRpcConnection(SocketAddress serverAddress) throws IOException,
			ConnectException
	{
		RpcConnection connection = new RpcConnection(serverAddress, packetManager, listener);
		connection.init();
		connectionManager.put(connection);
		return connection;
	}
	
	/**
	 * Gets the connector.
	 * 
	 * @return the connector
	 */
	public RpcConnector getConnector()
	{
		return connector;
	}
	
	/**
	 * Gets the packet manager.
	 * 
	 * @return the packet manager
	 */
	PacketManager getPacketManager()
	{
		return packetManager;
	}
	
	/**
	 * Start this client to manager connections and listen the socket events.
	 * 
	 * @throws RuntimeException
	 *             If start the client failed.
	 */
	public void start() throws RuntimeException
	{
		connectionManager.start();
		try
		{
			listener.initialize();
		} catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Notify connection closed.
	 * 
	 * @param connection
	 *            the connection
	 */
	void notifyConnectionClosed(RpcConnection connection)
	{
		this.connector.notifyConnectionClosed(connection);
	}
	
	/**
	 * Ping the remote address.
	 * 
	 * @param serverAddress
	 *            the server address
	 * @throws NetWorkException
	 *             the net work exception
	 */
	public void ping(SocketAddress serverAddress) throws NetWorkException
	{
		SocketChannel socketChannel = null;
		try
		{
			socketChannel = SocketChannel.open(serverAddress);
			ByteBuffer pingBuffer = ByteBuffer.allocate(1);
			pingBuffer.put(ProtocolType.PING.getType());
			pingBuffer.flip();
			socketChannel.write(pingBuffer);
			pingBuffer.clear();
			socketChannel.read(pingBuffer);
			pingBuffer.flip();
			ConnectionState connectionState = ConnectionState.valueOf(pingBuffer.get());
			switch (connectionState)
			{
				case SERVICE_UNAVALIABLE:
					throw new NetWorkException(new IOException(
							ConnectionState.SERVICE_UNAVALIABLE.name()));
				case UNKNOWN:

					break;
				
				default:
					break;
			}
		} catch (IOException e)
		{
			throw new NetWorkException(e);
		} finally
		{
			if (socketChannel != null && socketChannel.isOpen())
			{
				try
				{
					
					socketChannel.close();
				} catch (Throwable e)
				{
					// ignore it.
				}
			}
		}
	}
	
	/**
	 * Creates the transfer socket.
	 * 
	 * @param serverAddress
	 *            the server address
	 * @param argument
	 *            the argument
	 * @return the transfer socket
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @throws ConnectException
	 *             the connect exception
	 * @throws ServiceNotFoundException
	 *             the service not found exception
	 * @throws IllegalServiceException
	 *             the illegal service exception
	 * @throws UnsupportedVersionException
	 *             the unsupported version exception
	 */
	public TransferSocket createTransferSocket(SocketAddress serverAddress,
			TransferArgument argument) throws IOException, ConnectException,
			ServiceNotFoundException, IllegalServiceException, UnsupportedVersionException
	{
		ClientStreamConnection connection =
				new ClientStreamConnection(serverAddress, argument, connectionManager);
		connection.init();
		connectionManager.put(connection);
		try
		{
			return connection.createTransferSocket();
		} catch (IOException e)
		{
			connectionManager.remove(connection);
			throw e;
		}
	}
}
